// ==================================================================================
// Shared Genomics Project MPI Codebase
// Version 1.0 30/04/2010
//
// (c) 2010 University of Manchester all rights reserved
//
// This file is distributed under the GNU General Public License, Version 2.  
// Please see the file COPYING.txt for more details
// ==================================================================================

/*!
\file 
\brief Implementation of Chromosomal Model
*/

#include <stdlib.h>
#include <string.h>

#include "chromosomes.h"

// 22 autosomes + X + Y + XY + M + 0 missing code
#define NUMBER_HUMAN_CHROMOSONE_TYPES (22 + 2 + 2 + 1)
#define MAX_CHR_NAME_LENGTH 10

#ifndef my_itoa
#define my_itoa _itoa // C99 function
#endif

int chromosomes_init(struct chromosomes *chr) {
	if (chr != NULL) {
		chr->chr_code = NULL;
		chr->chr_haploid = NULL;
		chr->chr_map_keys = NULL;
		chr->chr_map_values = NULL;
		chr->chr_sex = NULL;
		chr->chr_Y = NULL;
		chr->isdefined = FALSE;
		chr->n = 0;
		chr->nMapValues = 0;

		return 1;
	}

	return 0;
}

int chromosomes_alloc_attributes(struct chromosomes *chr) {
	if (chr != NULL) {
		char** pCodes = NULL;
		int i = 0;

		if (!chr->isdefined) {	
			if (chr->n == 0) return 0;
			else {
				chr->chr_haploid = (BOOL*) malloc(sizeof(BOOL) * chr->n);
				chr->chr_sex = (BOOL*) malloc(sizeof(BOOL) * chr->n);
				chr->chr_Y = (BOOL*) malloc(sizeof(BOOL) * chr->n);

				if (chr->chr_haploid == NULL || chr->chr_sex == NULL || chr->chr_Y == NULL)
					return 0;

				// Allocate the CHR "name" string array.
				chr->chr_code = (char**) malloc(sizeof(char**) * chr->n);
				if (chr->chr_code != NULL) {
					for (i = 0, pCodes = chr->chr_code; i < chr->n; i++, pCodes++) {
						char *pChar = (char*) calloc(chr->n, MAX_CHR_NAME_LENGTH);
						*pCodes = pChar;
					}
				} else {
					return 0;
				}
			}
		}

		return 1;
	}

	return 0;
}

int	chromosomes_free_attributes(struct chromosomes *chr) {
	if (chr != NULL) {
		if (chr->n > 0) {
			// Allocate the primitive array fields.
			if (chr->chr_haploid != NULL) {
				free(chr->chr_haploid);
				chr->chr_haploid = NULL;
			}
			if (chr->chr_sex != NULL) {
				free(chr->chr_sex); 
				chr->chr_sex = NULL;
			}
			if (chr->chr_Y != NULL) {
				free(chr->chr_Y); 
				chr->chr_Y = NULL;
			}

			// free the chromosome name code.
			if (chr->chr_code != NULL) {
				char** pCodes = NULL;
				int i;

				for (i = 0, pCodes = chr->chr_code; i < chr->n; i++, pCodes++)
					free(*pCodes);
					
				free(chr->chr_code);
				chr->chr_code = NULL;
			}

			// free the name/index map (if any)
			if (chr->nMapValues > 0) {
				int i;

				if (chr->chr_map_values != NULL) {
					free(chr->chr_map_values);
					chr->chr_map_values = NULL;
				}
				for (i = 0; i < chr->nMapValues; i++) {
					if (chr->chr_map_keys[i] != NULL) free(chr->chr_map_keys[i]);
				}
				if (chr->chr_map_keys != NULL) {
					free(chr->chr_map_keys);
					chr->chr_map_keys = NULL;
				}
			}
			
			// Dealloc-ed so count drops back to zero.
			chr->isdefined = FALSE;
			chr->n = 0;

			return 1;
		} else
			return 0;
	}

	return 0;
}

int chromosomes_define_human(struct chromosomes *chr) {
	char _chr_code[MAX_CHR_NAME_LENGTH];
	int i;

	if (chr != NULL) {
		if (chr->isdefined) return 0;
		
		chr->n = NUMBER_HUMAN_CHROMOSONE_TYPES;
		chromosomes_alloc_attributes(chr);

		// Autosomes.
		for (i = 0; i <= 22; i++) {
			chr->chr_haploid[i] = FALSE;
			chr->chr_sex[i] = FALSE;
			chr->chr_Y[i] = FALSE;

			my_itoa(i, _chr_code, 10);
			strcpy(chr->chr_code[i], _chr_code);
			chromosomes_make_pair(chr->chr_code[i], i, chr);
		}

		// X chromosome
		chr->chr_sex[23] = TRUE;
		chr->chr_haploid[23] = FALSE;
		chr->chr_Y[23] = FALSE;
		strcpy(chr->chr_code[23], "X");
		chromosomes_make_pair("X", 23, chr);
		chromosomes_make_pair("x", 23, chr);
		chromosomes_make_pair("23", 23, chr);

		// Y chromosome
		chr->chr_sex[24] = FALSE;
		chr->chr_haploid[24] = TRUE;
		chr->chr_Y[24] = TRUE;
		strcpy(chr->chr_code[24], "Y");
		chromosomes_make_pair("Y", 24, chr);
		chromosomes_make_pair("y", 24, chr);
		chromosomes_make_pair("24", 24, chr);

		// XY chromosome
		chr->chr_sex[25] = FALSE;
		chr->chr_haploid[25] = FALSE;
		chr->chr_Y[25] = FALSE;
		strcpy(chr->chr_code[25], "XY");
		chromosomes_make_pair("XY", 25, chr);
		chromosomes_make_pair("xy", 25, chr);
		chromosomes_make_pair("25", 25, chr);

		// MT chromosome
		chr->chr_sex[26] = FALSE;
		chr->chr_haploid[26] = TRUE;
		chr->chr_Y[26] = FALSE;
		strcpy(chr->chr_code[26], "MT");
		chromosomes_make_pair("MT", 26, chr);
		chromosomes_make_pair("mt", 26, chr);
		chromosomes_make_pair("m", 26, chr);
		chromosomes_make_pair("M", 26, chr);
		chromosomes_make_pair("26", 26, chr);

		chr->isdefined = TRUE;

		return 1;
	}
	
	return 0;
}

int chromosomes_make_pair(const char* key, int index, struct chromosomes *chr) {
	if (chr != NULL) {
		if (chr->nMapValues == 0) {
			chr->chr_map_keys = (char**) malloc(sizeof(char**));
			*(chr->chr_map_keys) = (char*) malloc(sizeof(char) * MAX_CHR_NAME_LENGTH);
			chr->chr_map_values = (int*) malloc(sizeof(int));

			// Copy the values into the map.
			strcpy(*(chr->chr_map_keys), key);
			*(chr->chr_map_values) = index;
			chr->nMapValues++;
			
			return 1;
		}
		else {
			// increment existing array by 1.
			char **temp = (char**) realloc(chr->chr_map_keys, (sizeof(char**) * chr->nMapValues) + sizeof(char**));
			int *iTemp = (int *) realloc(chr->chr_map_values, (sizeof(int) * chr->nMapValues) + sizeof(int));

			if (temp != NULL && iTemp != NULL) {
				chr->chr_map_keys = temp;
				chr->chr_map_values = iTemp;

				// Use array index notation to assign the updated values to the map array.
				chr->chr_map_keys[chr->nMapValues] = (char*) malloc(sizeof(char) * MAX_CHR_NAME_LENGTH);
				strcpy(chr->chr_map_keys[chr->nMapValues], key);
				chr->chr_map_values[chr->nMapValues] = index;
				chr->nMapValues++;

				return 1;
			} 
		}
	}
	
	return 0;
}

int chromosomes_find(const char *key, struct chromosomes *chr) {
	int index = -1, i;

	if (key != NULL && chr != NULL) {	
		for (i = 0; i < chr->nMapValues; i++) {
			if (chr->chr_map_keys[i] != NULL) {
				if (strcmp(key, chr->chr_map_keys[i]) == 0) {
					index = chr->chr_map_values[i];
					break;
				}
			}
		}
	}

	return index;
}
